using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using System.IO;
using System.Collections;

namespace Rogue
{
    // the mud-style output log, plus a game console
    
    public class ConsoleWindow : MessageWindow
    {
        public static ConsoleWindow Instance = null;

        #region Private Member Variables
        private bool m_bActive;
        private string m_sCommandString;
        private Queue<string> m_CommandHistory;
        private Dictionary<string, CommandData> m_CommandList;
        private readonly string m_sCommandPrompt = ">> ";
        private int m_nCommandHistoryIndex = -1;

        private struct CommandData
        {
            public CommandDelegate Command;
            public string Note;

            public CommandData(CommandDelegate a_Delegate, string a_sNote)
            {
                Command = a_Delegate;
                Note = a_sNote;
            }

            public CommandData(CommandDelegate a_Delegate)
            {
                Command = a_Delegate;
                Note = "";
            }
        }
        #endregion

        #region Public Member Variables
        public bool IsActive
            { get { return m_bActive; } }
        #endregion

        public delegate void CommandDelegate(string[] a_aArgs);

        // Constructor
        public ConsoleWindow(
            Texture2D a_Tex,
            GraphicsDevice a_Dev,
            Point a_pPos,
            Point a_pCellMapSize,
            Point a_pCellScreenSize,
            Point a_pCharacterOffset,
            int a_nTextBufferSize
            )
            : base(a_Tex, a_Dev, a_pPos, a_pCellMapSize, a_pCellScreenSize, a_pCharacterOffset, a_nTextBufferSize)
        {
            Instance = this;

            m_sCommandString = "";
            m_CommandHistory = new Queue<string>();
            m_CommandList = new Dictionary<string, CommandData>();

            // Listen for keyboard input
            InputManager.Instance.OnKeyPressed += new InputManager.OnKeyPressedHandler(OnKeyPressed);

            BorderTop = true;
            BorderColor = new Color(60, 60, 60);
            DrawBackground = true;
            BackgroundColor = Color.Black;
            
            // Set up internal console commands
            RegisterFunction("CONSOLE.RUN", CONSOLE_RunScript);
            RegisterFunction("CONSOLE.LISTCOMMANDS", CONSOLE_ListCommands);
            RegisterFunction("CONSOLE.CLEAR", CONSOLE_Clear);
        }

        public void ResetToShowLines(int numLines)
        {
            int windowHeightPx = numLines * m_pCellScreenSize.Y;
            Position.Y = RogueGame.Instance.Window.ClientBounds.Height - windowHeightPx;

            Reset(m_aCellMap.GetLength(0), numLines, m_pCellScreenSize);
        }

        public void OnKeyPressed(Object a_Sender, InputManager.KeyEventArgs a_Args)
        {
            // Activate and deactivate the console
            if (a_Args.Key == Keys.OemTilde)
            {
                if (!a_Args.Repeating)
                {
                    m_nCommandHistoryIndex = -1;
                    m_bActive = !m_bActive;
                    if (m_bActive)
                    {
                        BackgroundColor = new Color(10, 10, 10);
                        ResetToShowLines(18);
                    }
                    else
                    {
                        BackgroundColor = Color.Black;
                        ResetToShowLines(6);
                    }

                    return;
                }
                else
                    return;
            }

            // Console script
            if (a_Args.Key == Keys.F12)
            {
                RunCommand("console.run console.script");
                return;
            }

            // Special commands
            if (m_bActive)
            {
                switch (a_Args.Key)
                {
                    case Keys.Enter:
                        RunCommand(m_sCommandString);
                        m_sCommandString = "";
                        return;

                    case Keys.Back:
                        if (a_Args.Shift)
                        {
                            m_sCommandString = "";
                            m_nCommandHistoryIndex = -1;
                            return;
                        }
                        else
                            RemoveCommandChar();
                        return;
                    
                    case Keys.Tab:
                        MatchCommandPrefix(m_sCommandString);
                        return;

                    case Keys.Up:
                        if ((m_nCommandHistoryIndex < m_CommandHistory.Count - 1) && (m_CommandHistory.Count > 0))
                            m_nCommandHistoryIndex++;
                        m_sCommandString = GetCommandFromHistory(m_nCommandHistoryIndex);
                        return;
                    
                    case Keys.Down:
                        if (m_nCommandHistoryIndex > -1)
                            m_nCommandHistoryIndex--;
                        m_sCommandString = GetCommandFromHistory(m_nCommandHistoryIndex);
                        return;

                    case Keys.PageUp:
                        if ((m_nMessageOffset - Height) >= -m_MessageBuffer.Count)
                            m_nMessageOffset--;
                        return;
                    
                    case Keys.PageDown:
                        if (m_nMessageOffset < 0)
                            m_nMessageOffset++;
                        return;

                    case Keys.End:
                        m_nMessageOffset = 0;
                        return;

                    default:
                        if (a_Args.Character != '\0')
                            AddCommandChar(a_Args.Character.ToString());
                        return;
                }
            }
        }
        #region Update
        public override void Update(GameTime dt)
        {
            base.Update(dt);

            if (m_bActive)
            {
                // Process generic keystrokes and draw the cursor
                DrawCursor(dt.TotalGameTime.TotalSeconds);
            }

            // console window autoshow/hide - too obnoxious.
            // Could show a color change or something?

            //if (!m_bActive)
            //{
            //    int lowestIndexYoung = -1;

            //    for (int i = 0; i < m_MessageBuffer.Count; ++i)
            //    {
            //        if (m_MessageBuffer[i].age < 3.0f)
            //        {
            //            lowestIndexYoung = i;
            //            break;
            //        }
            //    }

            //    int numNewLines = m_MessageBuffer.Count - lowestIndexYoung;
            //    if (numNewLines >= 6 && lowestIndexYoung != -1)
            //    {
            //        ResetToShowLines(Math.Max(6, Math.Min(18, numNewLines)));
            //    }
            //    else
            //    {
            //        ResetToShowLines(6);
            //    }
            //}
        }

        protected string GetCommandFromHistory(int a_nIndex)
        {
            if (a_nIndex < 0)
                return "";

            string[] aPreviousCommands = m_CommandHistory.ToArray();
            return aPreviousCommands[(aPreviousCommands.Length - 1) - a_nIndex];
        }

        #endregion

        #region Rendering
        public override void RenderCells()
        {
            DrawMessageBuffer(Height-1);  // Since this is only a message window, we want to fill it completely.
            
            // Draw a border for the command line
            int nDividerHeight;
            Rectangle rWindowBounds = WindowBounds;
            nDividerHeight = rWindowBounds.Bottom - CellScreenSize.Y;
            DrawLine(new Point(rWindowBounds.Left, nDividerHeight), new Point(rWindowBounds.Right, nDividerHeight), BorderColor);

            // Draw the command line items
            if (m_bActive)
                PutString(m_sCommandPrompt, 0, Height - 1, Color.Gold);
            else
                PutString(m_sCommandPrompt + " (press ~ to open or close the console)", 0, Height - 1, new Color(60, 60, 60));

            if (m_sCommandString != null)
            {
                if (m_bActive)
                    PutString(m_sCommandString, 3, Height - 1);
                else
                    PutString(m_sCommandString, 3, Height - 1, new Color(60, 60, 60));
            }

            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    Rectangle dest = GetRenderDest(x, y);

                    if (m_aCellMap[x, y].ForegroundColor.A > 0)
                        m_SpriteBatch.Draw(m_Texture, dest, m_aCellDefs[(int)m_aCellMap[x, y].ForegroundTile], m_aCellMap[x, y].ForegroundColor);
                }
            }
        }

        private void DrawCursor(double a_nTime)
        {
            double nRamp = Math.Sin(a_nTime * 20);
            if (nRamp > 0)
            {
                PutChar('_', m_sCommandPrompt.Length + m_sCommandString.Length, Height - 1);
            }
        }
        #endregion

        #region Text & Command String Manipulation
        protected void AddCommandChar(string a_sKeyString)
        {
            if (a_sKeyString.Length == 1)
                m_sCommandString += a_sKeyString;
        }

        private void RemoveCommandChar()
        {
            if (m_sCommandString.Length > 0)
                m_sCommandString = m_sCommandString.Remove(m_sCommandString.Length - 1);
        }

        public void PrintCommand(string a_sCommand)
        {
            PrintCommand(a_sCommand, Color.WhiteSmoke);
        }

        public void PrintCommand(string a_sCommand, Color a_Color)
        {
            AddMessage(">> " + a_sCommand, a_Color);
        }

        public void PrintError(string a_sCommand)
        {
            AddMessage("   " + a_sCommand, Color.Goldenrod);
        }

        public void PrintSubMessage(string a_sMessage, Color a_Color)
        {
            AddMessage("   " + a_sMessage, a_Color);
        }

        public void MatchCommandPrefix(string a_sPrefix)
        {
            if (a_sPrefix.Length <= 0)
                return;

            List<string> CommandList = new List<string>();
            foreach (KeyValuePair<string, CommandData> command in m_CommandList)
            {
                if (command.Key.StartsWith(a_sPrefix.ToLower()))
                    CommandList.Add(command.Key);
            }

            CommandList.Sort();

            if (CommandList.Count > 0)
            {
                if (CommandList.Count == 1)
                {
                    m_sCommandString = CommandList[0] + " ";
                    return;
                }
                else
                {
                    AddMessage("", Color.Gray);
                    PrintSubMessage("Matching Commands:", Color.Gray);

                    string sPrefix = CommandList[0];

                    for (int i = 0; i < (CommandList.Count); i++)
                    {
                        sPrefix = GetLongestCommonPrefix(sPrefix, CommandList[i]);
                        
                        //PrintSubMessage(sCommand, Color.Gray);
                        PrintError(CommandList[i]);
                    }

                    m_sCommandString = sPrefix;
                }
            }
        }

        // From: http://sanity-free.org/128/command_line_style_string_parser_in_csharp.html
        public string[] ParseCommand(string str) 
        {        
            if(str == null || !(str.Length > 0)) 
                return new string[0];

            int idx = str.Trim().IndexOf(" "); 
            
            if(idx == -1) 
                return new string[] { str };

            int count = str.Length;
            ArrayList list = new ArrayList(); 
            
            while(count > 0) 
            {
                if(str[0] == '"') 
                {
                    int temp = str.IndexOf("\"", 1, str.Length - 1);
                    while(str[temp - 1] == '\\') 
                    {
                        temp = str.IndexOf("\"", temp + 1, str.Length - temp - 1);
                    }
                    idx = temp + 1;
                }
                if(str[0] == '\'') 
                {   
                    int temp = str.IndexOf("\'", 1, str.Length - 1);
                    while(str[temp - 1] == '\\') 
                    {
                        temp = str.IndexOf("\'", temp + 1, str.Length - temp - 1);
                    }
                    idx = temp + 1;
                }
                string s = str.Substring(0, idx);
                int left = count - idx;
                str = str.Substring(idx, left).Trim();
                list.Add(s.Trim('"'));
                count = str.Length;
                idx = str.IndexOf(" ");
                if(idx == -1) 
                {
                    string add = str.Trim('"', ' ');
                    if(add.Length > 0) 
                    {
                        list.Add(add);
                    }
                    break;
                }
            }
            return (string[])list.ToArray(typeof(string));
        }

        public string GetLongestCommonPrefix(string a_sFirst, string a_sSecond)
        {
            int nIndex = 0;
            int nSizeOfSmallerString;
            string sPrefix = "";

            if (a_sFirst.Length > a_sSecond.Length)
                nSizeOfSmallerString = a_sSecond.Length;
            else
                nSizeOfSmallerString = a_sFirst.Length;

            while ((nIndex < nSizeOfSmallerString) && (a_sFirst[nIndex] == a_sSecond[nIndex]))
            {
                sPrefix = sPrefix + a_sFirst[nIndex];
                nIndex++;
            }
            return sPrefix;    
        }
        #endregion

        #region Command Registration & Execution
         public void RunCommand(string a_sCommand)
        {
            string[] aCommandElements = ParseCommand(a_sCommand);
           

            if (a_sCommand.Length > 0)
            {
                m_CommandHistory.Enqueue(a_sCommand);
                if (m_CommandHistory.Count > 32)
                    m_CommandHistory.Dequeue();

                m_nCommandHistoryIndex = -1;

               string sCommand = aCommandElements[0].TrimEnd(' ').ToLower();

                // Get the arguments
                List<string> aArgs = new List<string>();

                for (int i = 1; i < aCommandElements.Length; i++)
                {
                    aArgs.Add(aCommandElements[i]);
                }

                PrintCommand(a_sCommand);
                if (m_CommandList.ContainsKey(sCommand))
                {
                    try
                    {
                        m_CommandList[sCommand].Command.Invoke(aArgs.ToArray());
                    }
                    catch (System.Exception e)
                    {
                        PrintError("COMMAND EXCEPTION: " + e.Message);
                    }
                    return;
                }
                else
                {
                    PrintError("COMMAND NOT RECOGNIZED: " + sCommand);
                    return;
                }
            }
        }
                
        public void RunConsoleScript(string a_sScriptName)
        {
            if (!File.Exists(Path.Combine(StorageContainer.TitleLocation, a_sScriptName.ToLower())))
            {
                PrintError("MISSING FILE: \"" + a_sScriptName + "\"");
                return;
            }

            FileStream ScriptStream = File.Open(Path.Combine(StorageContainer.TitleLocation, a_sScriptName.ToLower()), FileMode.Open, FileAccess.Read);
            if (ScriptStream.Length > 0)
            {
                System.IO.StreamReader Reader = new System.IO.StreamReader(ScriptStream);

                string sCurrentLine;
                while ((sCurrentLine = Reader.ReadLine()) != null)
                    RunCommand(sCurrentLine);
            }
            else
            {
                PrintError("MISSING FILE: \"" + a_sScriptName + "\"");
            }
            ScriptStream.Close();   
        }
        
        public void RegisterFunction(string a_sCommandString, CommandDelegate a_Delegate)
        {
            if (!m_CommandList.ContainsKey(a_sCommandString.ToLower()))
            {
                m_CommandList.Add(a_sCommandString.ToLower(), new CommandData(a_Delegate));
            }
            else
            {
                m_CommandList[a_sCommandString.ToLower()] = new CommandData(a_Delegate);
            }
        }

        public void RegisterFunction(string a_sCommandString, string a_sNote, CommandDelegate a_Delegate)
        {
            if (!m_CommandList.ContainsKey(a_sCommandString))
            {
                m_CommandList.Add(a_sCommandString.ToLower(), new CommandData(a_Delegate, a_sNote));
            }
        }
        #endregion

        #region Console Commands
        private void CONSOLE_RunScript(string[] a_sArgs)
        {
            RunConsoleScript(a_sArgs[0]);
        }

        private void CONSOLE_ListCommands(string[] a_sArgs)
        {
            AddMessage("", Color.WhiteSmoke);
            PrintSubMessage("COMMAND LIST:", Color.Gray);

            List<string> CommandList = new List<string>();
            foreach (KeyValuePair<string, CommandData> command in m_CommandList)
            {
                CommandList.Add(command.Key);
            }

            CommandList.Sort();
           
            foreach (string sCommand in CommandList)
            {
                if (m_CommandList[sCommand].Note.Length > 0)
                    ConsoleWindow.Instance.PrintError(sCommand + " : " + m_CommandList[sCommand].Note);
                else
                    ConsoleWindow.Instance.PrintError(sCommand);
            }
        }

        private void CONSOLE_Help(string[] a_sArgs)
        {
            if (m_CommandList.ContainsKey(a_sArgs[0]))
            {
                PrintError(m_CommandList[a_sArgs[0]].Note);
            }
        }

        private void CONSOLE_Clear(string[] a_sArgs)
        {
            m_MessageBuffer.Clear();
        }
        #endregion
    }
}
